package vip.manda.framework.core;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.io.resource.NoResourceException;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.lang.caller.CallerUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import vip.manda.framework.log.Log;
import vip.manda.framework.log.LogFactory;
import vip.manda.framework.log.LogPrinter;

import java.lang.annotation.*;
import java.lang.reflect.AnnotatedElement;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author hongda.li 2022-04-10 22:59
 */
public class Application {
    public static final String VERSION = "sunrise.1.4";
    private static Class<?> main = Application.class;
    private static final Set<Class<?>> CLASSES = new HashSet<>();
    private static final Set<Class<?>> INTERFACES = new HashSet<>();
    private static final Log log = LogFactory.getLog();
    private static final TimeInterval INTERVAL = new TimeInterval();
    public static Class<?> getMain() {
        return main;
    }
    public static Set<Class<?>> getClasses() {
        return CLASSES;
    }
    public static Set<Class<?>> getInterfaces() {
        return INTERFACES;
    }
    public static TimeInterval getInterval() {
        return INTERVAL;
    }
    public static void run() {
        run(CallerUtil.getCallerCaller());
    }
    public static void run(Class<?> main) {
        Application.main = main;
        INTERVAL.start();
        showBanner();
        log.info("CogoFramework.Version[{}]", VERSION);
        log.info("Application run start[{}]", main.getName());
        handleRunner();
        log.info("Application run end[{}ms]", INTERVAL.intervalMs());
    }

    private static void handleRunner() {
        Configs.init();
        log.info("Obtain main class[{}]", main.getName());
        log.info("Initializing...");
        CLASSES.addAll(ClassUtil.scanPackage(ClassUtil.getPackage(main)));
        handleImport();
        handleScanner();
        handleRemove();
        CLASSES.forEach(clazz -> log.info("Obtain class[{}]", clazz.getName()));
        handleFunction();
    }

    private static void showBanner() {
        String banner;
        try {
            banner = ResourceUtil.readUtf8Str("banner.txt");
        } catch (NoResourceException e) {
            banner = "   _____                      _____                   _             \n" +
                    "  / ____|                    |  __ \\                 (_)            \n" +
                    " | |     ___   __ _  ___     | |__) |   _ _ __  _ __  _ _ __   __ _ \n" +
                    " | |    / _ \\ / _` |/ _ \\    |  _  / | | | '_ \\| '_ \\| | '_ \\ / _` |\n" +
                    " | |___| (_) | (_| | (_) |   | | \\ \\ |_| | | | | | | | | | | | (_| |\n" +
                    "  \\_____\\___/ \\__, |\\___/    |_|  \\_\\__,_|_| |_|_| |_|_|_| |_|\\__, |\n" +
                    "               __/ |                                           __/ |\n" +
                    "              |___/                                           |___/ ";
        }
        System.out.format("\33[%d;2m%-12s", LogPrinter.INFO, banner);
        System.out.format("%s", LogPrinter.LINE_SEPARATOR);
    }

    private static void handleImport() {
        CLASSES.stream()
                .filter(clazz -> AnnotationUtil.hasAnnotation(clazz, Import.class))
                .collect(Collectors.toSet())
                .forEach(clazz -> getAnnotations(clazz, Import.class)
                        .forEach(importClazz -> CLASSES.addAll(Arrays.asList(importClazz.value()))));
    }

    private static void handleScanner() {
        CLASSES.stream()
                .filter(clazz -> AnnotationUtil.hasAnnotation(clazz, Scanner.class))
                .collect(Collectors.toSet())
                .forEach(clazz -> getAnnotations(clazz, Scanner.class)
                        .forEach(scanner -> Arrays.asList(scanner.value())
                                .forEach(packageName -> CLASSES.addAll(ClassUtil.scanPackage(packageName)))));
    }

    private static void handleRemove() {
        CLASSES.stream()
                .filter(clazz -> AnnotationUtil.hasAnnotation(clazz, Remove.class))
                .collect(Collectors.toSet()).forEach(clazz -> Arrays.asList(AnnotationUtil.getAnnotationValue(clazz, Remove.class)).forEach(CLASSES::remove));
        INTERFACES.addAll(CLASSES.stream()
                .filter(clazz -> clazz.isAnnotation() || clazz.isInterface())
                .collect(Collectors.toSet()));
        INTERFACES.forEach(CLASSES::remove);
    }

    private static void handleFunction() {
        CLASSES.stream().filter(clazz -> AnnotationUtil.hasAnnotation(clazz, Function.class))
                .sorted(Comparator.comparingInt(clazz -> AnnotationUtil.getAnnotationValue(clazz
                        , Function.class
                        , Function.Common.ORDER)))
                .forEach(function -> ReflectUtil.invoke(ReflectUtil.newInstance(function), ReflectUtil.getMethodByName(function, Function.Common.METHOD), CLASSES));
    }

    public static <T> List<T> getAnnotations(AnnotatedElement element, Class<T> type) {
        List<T> annotationList = new ArrayList<>();
        recursion(annotationList, element.getAnnotations(), type);
        return annotationList;
    }

    private static <T> void recursion(List<T> list, Annotation[] annotations, Class<T> type) {
        for (Annotation annotation : annotations) {
            Class<? extends Annotation> clazz = annotation.getClass();
            if (type.isAssignableFrom(clazz)) {
                list.add((T) annotation);
            } else if (!Retention.class.isAssignableFrom(clazz)
                    && !Target.class.isAssignableFrom(clazz)
                    && !Documented.class.isAssignableFrom(clazz)
                    && !Repeatable.class.isAssignableFrom(clazz)
                    && !Inherited.class.isAssignableFrom(clazz)) {
                recursion(list, annotation.annotationType().getAnnotations(), type);
            }
        }
    }
}
